//-----------------------------------------------------------------------
// <copyright file="EventBroker.cs" company="iron9light">
//      Copyright (c) iron9light. All rights reserved.
// </copyright>
// <author>iron9light</author>
//-----------------------------------------------------------------------

namespace EventBroker
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// The event broker.
    /// </summary>
    public class EventBroker : IEventBroker
    {
        private readonly Dictionary<string, IEventTopic> eventTopics = new Dictionary<string, IEventTopic>();

        /// <summary>
        /// Gets the registered events.
        /// </summary>
        /// <value>The registered events.</value>
        public IEnumerable<string> RegisteredEvents
        {
            get
            {
                this.Clean();
                foreach (string eventName in this.eventTopics.Keys)
                {
                    yield return eventName;
                }
            }
        }

        /// <summary>
        /// Registers the publication.
        /// </summary>
        /// <param name="eventTopicName">Name of the published event.</param>
        /// <param name="publisher">The publisher.</param>
        /// <param name="eventName">Name of the event.</param>
        public void RegisterPublication(string eventTopicName, object publisher, string eventName)
        {
            Guard.ArgumentNotNull(publisher, "publisher");
            Guard.ArgumentNotNullOrEmptyString(eventName, "eventName");

            IPublication publication = this.CreatePublication(publisher, eventName);
            IEventTopic @event = this.GetEvent(eventTopicName);
            @event.AddPublication(publication);
        }

        /// <summary>
        /// Unregisters the publication.
        /// </summary>
        /// <param name="eventTopicName">Name of the published event.</param>
        /// <param name="publisher">The publisher.</param>
        /// <param name="eventName">Name of the event.</param>
        public void UnregisterPublication(string eventTopicName, object publisher, string eventName)
        {
            IEventTopic @event = this.GetEvent(eventTopicName);
            @event.RemovePublication(publisher, eventName);
            this.Clean();
        }

        /// <summary>
        /// Unregisters the publisher.
        /// </summary>
        /// <param name="publisher">The publisher.</param>
        public void UnregisterPublisher(object publisher)
        {
            foreach (IEventTopic @event in this.eventTopics.Values)
            {
                @event.RemovePublisher(publisher);
            }

            this.Clean();
        }

        /// <summary>
        /// Registers the subscription.
        /// </summary>
        /// <param name="eventTopicName">Name of the event topic.</param>
        /// <param name="subscriber">The subscriber.</param>
        /// <param name="methodName">Name of the method.</param>
        public void RegisterSubscription(string eventTopicName, object subscriber, string methodName)
        {
            this.RegisterSubscription(eventTopicName, subscriber, methodName, null);
        }

        /// <summary>
        /// Registers the subscription.
        /// </summary>
        /// <param name="eventTopicName">Name of the event topic.</param>
        /// <param name="subscriber">The subscriber.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="parameterTypes">The parameter types.</param>
        public void RegisterSubscription(string eventTopicName, object subscriber, string methodName, Type[] parameterTypes)
        {
            Guard.ArgumentNotNull(subscriber, "subscriber");
            Guard.ArgumentNotNullOrEmptyString(methodName, "methodName");

            IEventTopic @event = this.GetEvent(eventTopicName);
            ISubscription subscription = this.CreateSubscription(subscriber, methodName, parameterTypes);
            @event.AddSubscription(subscription);
        }

        /// <summary>
        /// Unregisters the subscriber.
        /// </summary>
        /// <param name="eventTopicName">Name of the event topic.</param>
        /// <param name="subscriber">The subscriber.</param>
        /// <param name="methodName">Name of the method.</param>
        public void UnregisterSubscription(string eventTopicName, object subscriber, string methodName)
        {
            IEventTopic @event = this.GetEvent(eventTopicName);
            @event.RemoveSubscription(subscriber, methodName);
            this.Clean();
        }

        /// <summary>
        /// Unregisters the subscription.
        /// </summary>
        /// <param name="subscriber">The subscriber.</param>
        public void UnregisterSubscriber(object subscriber)
        {
            foreach (IEventTopic @event in this.eventTopics.Values)
            {
                @event.RemoveSubscription(subscriber);
            }

            this.Clean();
        }

        /// <summary>
        /// Called to free resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Called to free resources.
        /// </summary>
        /// <param name="disposing">Should be true when calling from Dispose().</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                foreach (IEventTopic eventTopic in this.eventTopics.Values)
                {
                    eventTopic.Dispose();
                }
            }
        }

        /// <summary>
        /// Creates the publication.
        /// </summary>
        /// <param name="publisher">The publisher.</param>
        /// <param name="eventName">Name of the event.</param>
        /// <returns>The publication.</returns>
        protected virtual IPublication CreatePublication(object publisher, string eventName)
        {
            return new Publication(publisher, eventName);
        }

        /// <summary>
        /// Creates the subscription.
        /// </summary>
        /// <param name="subscriber">The subscriber.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="parameterTypes">The parameter types.</param>
        /// <returns>The subscription.</returns>
        protected virtual ISubscription CreateSubscription(object subscriber, string methodName, Type[] parameterTypes)
        {
            return new Subscription(subscriber, methodName, parameterTypes);
        }

        /// <summary>
        /// Gets the event.
        /// </summary>
        /// <param name="eventTopicName">Name of the event.</param>
        /// <returns>The event.</returns>
        private IEventTopic GetEvent(string eventTopicName)
        {
            if (!this.eventTopics.ContainsKey(eventTopicName))
            {
                this.eventTopics[eventTopicName] = new EventTopic(eventTopicName);
            }

            return this.eventTopics[eventTopicName];
        }

        /// <summary>
        /// Removes the dead events.
        /// </summary>
        private void Clean()
        {
            List<string> deadEvents = new List<string>();
            foreach (KeyValuePair<string, IEventTopic> eventTopicPair in this.eventTopics)
            {
                if (eventTopicPair.Value.PublicationCount == 0 && eventTopicPair.Value.SubscriptionCount == 0)
                {
                    deadEvents.Add(eventTopicPair.Key);
                    eventTopicPair.Value.Dispose();
                }
            }

            foreach (string eventTopicName in deadEvents)
            {
                this.eventTopics.Remove(eventTopicName);
            }
        }
    }
}